<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>MathBox - 3D Grapher</title>
  <script src="mathbox-bundle.js"></script>
  <script src="dat.gui.js"></script>
  
<!-- http://silentmatt.com/javascript-expression-evaluator/ -->
<script src="parser.js"></script>

  <link rel="stylesheet" href="mathbox.css">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1">
</head>
<body>
  <script>
    var mathbox = mathBox({
      plugins: ['core', 'controls', 'cursor', 'mathbox'],
      controls: {klass: THREE.OrbitControls}
    });
    if (mathbox.fallback) throw "WebGL not supported"

    var three = mathbox.three;
    three.renderer.setClearColor(new THREE.Color(0xFFFFFF), 1.0);

	var graphData, view;
	
	var xFunctionText = "cos(u)*(a + b*cos(v))";
	var yFunctionText = "sin(u)*(a + b*cos(v))";
	var zFunctionText = "b*sin(v)";
	
	var a = 1, b = 0.5;
	var	xMin = -3, xMax = 3, yMin = -2,	yMax = 2, zMin = -3, zMax = 3;
	var uMin = 0, uMax = 6.282, vMin = 0, vMax = 6.282;
	
	// start of updateGraph function ==============================================================
	var updateGraphFunc = function() 
	{ 
		var xFunc = Parser.parse( xFunctionText ).toJSFunction( ['u','v'] );
		var yFunc = Parser.parse( yFunctionText ).toJSFunction( ['u','v'] );
		var zFunc = Parser.parse( zFunctionText ).toJSFunction( ['u','v'] );
		graphData.set("expr", 
		    function (emit, u, v, i, j, t) 
			{ 
			    emit( xFunc(u,v), zFunc(u,v), yFunc(u,v) );
			}
		);
		
		view.set("range", [[xMin, xMax], [yMin, yMax], [zMin,zMax]]); 
		domain.set("range", [[uMin, uMax], [vMin, vMax]]  );
		
		// start of color options =============================================
		if (graphColorStyle == "Solid Blue")
		{
			// just a solid blue color			
			domainColors.set("expr", 
				function (emit, u,v, i,j, t) 
				{ 
					emit( 0.5, 0.5, 1.0, 1.0 );
				}
			);
		}
		else if (graphColorStyle == "Red U, Green V")
		{
			// increased u/v -> increased red/green			
			domainColors.set("expr", 
				function (emit, u,v, i,j, t) 
				{
					var percentU = (u - uMin) / (uMax - uMin);
					var percentV = (v - vMin) / (vMax - vMin);
					emit(percentU, percentV, 0.0, 1.0);
		   
				}
			);
		}
		else if (graphColorStyle == "Rainbow Along U")
		{
			// 		
			domainColors.set("expr", 
				function (emit, u,v, i,j, t) 
				{
					var percent = (u - uMin) / (uMax - uMin);
					var color = new THREE.Color( 0xffffff );
					color.setHSL( percent, 1, 0.5 );
					emit( color.r, color.g, color.b, 1.0 );
				}
			);
		}
		else if (graphColorStyle == "Rainbow Along V")
		{
			// 		
			domainColors.set("expr", 
				function (emit, u,v, i,j, t) 
				{
					var percent = (v - vMin) / (vMax - vMin);
					var color = new THREE.Color( 0xffffff );
					color.setHSL( percent, 1, 0.5 );
					emit( color.r, color.g, color.b, 1.0 );
				}
			);
		}
		// end of color options =============================================
		
	}
	// end of updateGraph function ==============================================================
	
	var updateGraph = function() { updateGraphFunc(); };
	

	// setting proxy:true allows interactive controls to override base position
	var camera = mathbox.camera( { proxy: true, position: [2, 1, 2] } );

	 // save as variable to adjust later
    view = mathbox.cartesian(
	  {
        range: [[xMin, xMax], [yMin, yMax], [zMin,zMax]],
        scale: [2,1,2],
      }
	);

	// axes
	var xAxis = view.axis( {axis: 1, width: 8, detail: 40, color:"red"} );
    var xScale = view.scale( {axis: 1, divide: 10, nice:true, zero:true} );
    var xTicks = view.ticks( {width: 5, size: 15, color: "red", zBias:2} );
    var xFormat = view.format( {digits: 2, font:"Arial", weight: "bold", style: "normal", source: xScale} );
    var xTicksLabel = view.label( {color: "red", zIndex: 0, offset:[0,-20], points: xScale, text: xFormat} );
	
	var yAxis = view.axis( {axis: 3, width: 8, detail: 40, color:"green"} );
    var yScale = view.scale( {axis: 3, divide: 5, nice:true, zero:false} );
    var yTicks = view.ticks( {width: 5, size: 15, color: "green", zBias:2} );
    var yFormat = view.format( {digits: 2, font:"Arial", weight: "bold", style: "normal", source: yScale} );
    var yTicksLabel = view.label( {color: "green", zIndex: 0, offset:[0,0], points: yScale, text: yFormat} );
	
	var zAxis = view.axis( {axis: 2, width: 8, detail: 40, color:"blue"} );
    var zScale = view.scale( {axis: 2, divide: 5, nice:true, zero:false} );
    var zTicks = view.ticks( {width: 5, size: 15, color: "blue", zBias:2} );
    var zFormat = view.format( {digits: 2, font:"Arial", weight: "bold", style: "normal", source: zScale} );
    var zTicksLabel = view.label( {color: "blue", zIndex: 0, offset:[0,0], points: zScale, text: zFormat} );
	
	view.grid( {axes:[1,3], width: 2, divideX: 20, divideY: 20, opacity:0.25} );
	
	// need separate range for surface domain values. can't use values from view.
	
    var domain = mathbox.cartesian(
	  {
        range: [[uMin, uMax], [vMin, vMax]]
      }
	);
	
	var resolution = 32;
	var graphData = domain.area({
		width: resolution, height: resolution,
        // expr: set later
		axes: [1,2],  // u,v
        channels: 3,  // 3D space
    });

	var domainColors = domain.area({
		width: resolution, height: resolution,
		// expr: set later
 		channels: 4, // RGBA
    });
	
	var surfaceViewFill = view.surface({
	  points: graphData,
	  fill: true, shaded: false, lineX: false, lineY: false,
      color: "white", colors: domainColors,
    });
	
	var surfaceViewLine = view.surface({
	  points: graphData,
	  fill: false, shaded: false, lineX: true, lineY: true,
      color: "black",
    });
	  
    // GUI controls
	
	var gui = new dat.GUI();
	
	var xFuncGUI = gui.add( this, 'xFunctionText' ).name('x = f(u,v) = ');
	var yFuncGUI = gui.add( this, 'yFunctionText' ).name('y = g(u,v) = ');
	var zFuncGUI = gui.add( this, 'zFunctionText' ).name('z = h(u,v) = ');
	
	var folder0 = gui.addFolder('Parameters');
	var aGUI = folder0.add( this, 'a' ).min(0).max(5).step(0.01).name('a = ');
	var bGUI = folder0.add( this, 'b' ).min(0).max(5).step(0.01).name('b = ');
	folder0.open();
	
	
	var folder2 = gui.addFolder('Parameter Range');
	var uMinGUI = folder2.add( this, 'uMin' ).onChange( updateGraphFunc );
	var uMaxGUI = folder2.add( this, 'uMax' ).onChange( updateGraphFunc );
	var vMinGUI = folder2.add( this, 'vMin' ).onChange( updateGraphFunc );
	var vMaxGUI = folder2.add( this, 'vMax' ).onChange( updateGraphFunc );
	folder2.open();
	
	var folderP = gui.addFolder("Preset Equations");
	folderP.open();
	
	var presetFunc1 = function() { 
		xFuncGUI.setValue("u"); yFuncGUI.setValue("v"); zFuncGUI.setValue("0"); 
		uMinGUI.setValue(-1); uMaxGUI.setValue(1); 
		vMinGUI.setValue(-1); vMaxGUI.setValue(1);
		updateGraph(); 
		};
	var preset1GUI = folderP.add( this, "presetFunc1" ).name("Square");
	
	var presetFunc11 = function() { 
		xFuncGUI.setValue("1.5 * u - 1.0 * v + 1.0"); yFuncGUI.setValue("1.0 * u + 1.0 * v + 0.5"); zFuncGUI.setValue("1.0 * u + 1.0 * v + 1.0"); 
		uMinGUI.setValue(-1); uMaxGUI.setValue(1); 
		vMinGUI.setValue(-1); vMaxGUI.setValue(1);
		updateGraph(); 
		};
	var preset11GUI = folderP.add( this, "presetFunc11" ).name("Plane");
	
	var presetFunc2 = function() { 
		xFuncGUI.setValue("cos(u)"); yFuncGUI.setValue("sin(u)"); zFuncGUI.setValue("v");
		uMinGUI.setValue(0); uMaxGUI.setValue(6.282); 
		vMinGUI.setValue(-1); vMaxGUI.setValue(1);
		updateGraph(); 
		};
	var preset2GUI = folderP.add( this, "presetFunc2" ).name("Cylinder");
	
	var presetFunc3 = function() { 
		xFuncGUI.setValue("sin(u) * cos(v)"); yFuncGUI.setValue("sin(u) * sin(v)"); zFuncGUI.setValue("cos(u)");
		uMinGUI.setValue(0); uMaxGUI.setValue(3.141); 
		vMinGUI.setValue(0); vMaxGUI.setValue(6.282);
		updateGraph(); 
		};
	var preset3GUI = folderP.add( this, "presetFunc3" ).name("Sphere");
	
	var presetFunc4 = function() { 
		xFuncGUI.setValue("u"); yFuncGUI.setValue("0.25 * u^2 * cos(v)"); zFuncGUI.setValue("0.25 * u^2 * sin(v)");
		uMinGUI.setValue(0); uMaxGUI.setValue(3); 
		vMinGUI.setValue(3.14/2); vMaxGUI.setValue(6.282 + 3.14/2);
		updateGraph(); 
		};
	var preset4GUI = folderP.add( this, "presetFunc4" ).name("Solid of Revolution");
	
	var presetFunc5 = function() { 
		xFuncGUI.setValue("cos(u)*(a + b*cos(v))"); yFuncGUI.setValue("sin(u)*(a + b*cos(v))"); zFuncGUI.setValue("b*sin(v)");
		uMinGUI.setValue(0); uMaxGUI.setValue(6.282); 
		vMinGUI.setValue(0); vMaxGUI.setValue(6.282);
		aGUI.setValue(1); bGUI.setValue(0.5);
		updateGraph(); 
		};
	var preset5GUI = folderP.add( this, "presetFunc5" ).name("Torus");
	
	var presetFunc6 = function() { 
		xFuncGUI.setValue("u * cos(v)"); yFuncGUI.setValue("u * sin(v)"); zFuncGUI.setValue("0.5 * v");
		uMinGUI.setValue(-2); uMaxGUI.setValue(2); 
		vMinGUI.setValue(-3.14); vMaxGUI.setValue(3.14);
		updateGraph(); 
		};
	var preset6GUI = folderP.add( this, "presetFunc6" ).name("Helical");
	
	var presetFunc7 = function() { 
		xFuncGUI.setValue("cos(u)^3 * cos(v)^3"); yFuncGUI.setValue("sin(u)^3 * cos(v)^3"); zFuncGUI.setValue("sin(v)^3");
		uMinGUI.setValue(0); uMaxGUI.setValue(3.141); 
		vMinGUI.setValue(0); vMaxGUI.setValue(6.282);
		updateGraph(); 
		};
	var preset7GUI = folderP.add( this, "presetFunc7" ).name("Astroid");
	
	
	
	var folder1 = gui.addFolder('Window Range');
	var xMinGUI = folder1.add( this, 'xMin' );
	var xMaxGUI = folder1.add( this, 'xMax' );
	var zMinGUI = folder1.add( this, 'zMin' ).name("yMin");
	var zMaxGUI = folder1.add( this, 'zMax' ).name("yMax");
	var yMinGUI = folder1.add( this, 'yMin' ).name("zMin");
	var yMaxGUI = folder1.add( this, 'yMax' ).name("zMax");
	folder1.close();
	
	var graphColorStyle = "Solid Blue";
	var graphColorStyleList = ["Solid Blue", "Red U, Green V", "Rainbow Along U", "Rainbow Along V"];
	var graphColorGUI = gui.add( this, "graphColorStyle", graphColorStyleList ).name('Graph style').onChange( updateGraphFunc );
	
	gui.add( this, 'updateGraph' ).name("Update Graph");
	
	gui.open();
	
	
	updateGraph();
	
	</script>
</body>
</html>
